package cn.tmpan.project.utils;


import com.jpay.util.HttpUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.*;

/**
 * 微信服务接口提供方
 *
 * @author tieMinPan
 * @date 2019-07-04 10:09
 */
public class WeChatServerUtil {

    /**
     * 获取微信全局token
     * {"access_token":"ACCESS_TOKEN","expires_in":7200}
     *
     * @param appId
     * @param secret
     * @return token
     */
    public static String getGlobalAccessToken(String appId, String secret) {
        HashMap hashMap = new HashMap(16);
        hashMap.put("grant_type", "client_credential");
        hashMap.put("appid", appId);
        hashMap.put("secret", secret);
        String resStr = HttpUtils.get(PropertiesUtil.getProperty("GET_ACCESS_TOKEN_URL"), hashMap);
        Map resMap = JsonUtil.jsonToMap(resStr);
        if (null == resMap.get("access_token")) {
            throw new RuntimeException("获取全局access_token错误!返回结果:" + resStr);
        }
        return resMap.get("access_token").toString();
    }


    /**
     * 获取公众号js_sdk认证
     *
     * @param token
     * @return
     */
    public static String getJsApiTicketByMp(String token) {
        HashMap hashMap = new HashMap(16);
        hashMap.put("type", "jsapi");
        hashMap.put("access_token", token);
        String resStr = HttpUtils.get(PropertiesUtil.getProperty("GET_JS_API_TICKET_URL"), hashMap);
        Map resMap = JsonUtil.jsonToMap(resStr);
        if (null == resMap.get("ticket")) {
            throw new RuntimeException("ticket获取错误!返回结果:" + resStr);
        }
        return resMap.get("ticket").toString();
    }


    /**
     * 获取公众号用户openId和临时access_token
     * <p>
     * {
     * "access_token":"ACCESS_TOKEN",
     * "expires_in":7200,
     * "refresh_token":"REFRESH_TOKEN",
     * "openid":"OPENID",
     * "scope":"SCOPE"
     * }
     * </p>
     *
     * @param appId
     * @param secret 秘钥
     * @param code   认证code
     * @return
     */
    public static Map getUserOpenIdAndItemAccessTokenByMp(String appId, String secret, String code) {
        HashMap hashMap = new HashMap(16);
        hashMap.put("grant_type", "authorization_code");
        hashMap.put("appid", appId);
        hashMap.put("secret", secret);
        hashMap.put("code", code);
        String resStr = HttpUtils.get(PropertiesUtil.getProperty("GET_MP_OPENID_URL"), hashMap);
        Map resMap = JsonUtil.jsonToMap(resStr);
        if (null == resMap.get("openid")) {
            throw new RuntimeException("获取公众号用户openId和临时access_token异常!返回结果:" + resStr);
        }
        return resMap;
    }


    /**
     * <p>
     * {
     * "openid":" OPENID",
     * " nickname": NICKNAME,
     * "sex":"1",
     * "province":"PROVINCE"
     * "city":"CITY",
     * "country":"COUNTRY",
     * "headimgurl":       "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
     * "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
     * "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
     * }
     * </p>
     *
     * @param openId      用户的唯一标识
     * @param accessToken 网页授权接口调用凭证,注意：此access_token与基础支持的access_token不同(授权作用域必须为:snsapi_userinfo)
     * @return
     */
    public static Map getUserInfoByMpAndScope(String openId, String accessToken) {
        HashMap hashMap = new HashMap(16);
        hashMap.put("access_token", accessToken);
        hashMap.put("openid", openId);
        String resStr = HttpUtils.get(PropertiesUtil.getProperty("GET_USER_INFO_BY_SCOPE_IS_SNS_API_USER_INFO_API_URL"), hashMap);
        Map resMap = JsonUtil.jsonToMap(resStr);
        if (null == resMap.get("openid")) {
            throw new RuntimeException("临时AccessToken获取公众号用户信息异常!返回结果:" + resStr);
        }
        if (null != resMap.get("headimgurl")) {
            resMap.put("headimgurlStr", resMap.get("headimgurl").toString().replace("\\", ""));
        }
        return resMap;
    }

    /**
     * 获取公众号用户信息 包含是否订阅 如果未订阅 无法获取用户信息
     * 参考链接
     * https://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId}
     * <p>
     * {
     * "subscribe": 1, #用户是否订阅该公众号标识，值为0时，代表此用户没有关注该公众号，拉取不到其余信息。
     * "openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M",
     * "nickname": "Band",
     * "sex": 1,
     * "language": "zh_CN",
     * "city": "广州",
     * "province": "广东",
     * "country": "中国",
     * "headimgurl":"http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
     * "subscribe_time": 1382694957,
     * "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
     * "remark": "",
     * "groupid": 0,
     * "tagid_list":[128,2],
     * "subscribe_scene": "ADD_SCENE_QR_CODE",
     * "qr_scene": 98765,
     * "qr_scene_str": ""
     * }
     * </p>
     *
     * @param openId
     * @param accessToken 全局token
     * @return
     */
    public static Map getUserInfoByMp(String openId, String accessToken) {
        HashMap hashMap = new HashMap(16);
        hashMap.put("access_token", accessToken);
        hashMap.put("openid", openId);
        String resStr = HttpUtils.get(PropertiesUtil.getProperty("GET_WE_CHAT_USER_INFO_API"), hashMap);
        Map resMap = JsonUtil.jsonToMap(resStr);
        if (null == resMap.get("subscribe")) {
            throw new RuntimeException("AccessToken获取公众号用户信息异常!返回结果:" + resStr);
        }
        if (resMap.get("headimgurl") != null) {
            resMap.put("headimgurlStr", resMap.get("headimgurl").toString().replace("\\", ""));
        }
        return resMap;
    }

    /**
     * 获取小程序用户openId
     * <p>
     * {
     * "errno": 0,
     * "errdesc": "",
     * "data": {
     * "session_key": "WFFpxZkfvg0xPjZ6oI7YzA==",
     * "openid": "ooBzj5Ot2GmNgty-tKtkT567aGiM"
     * }
     * }
     * </p>
     *
     * @param appId  小程序APPID
     * @param secret 小程序秘钥
     * @param jsCode 小程序认证code
     * @return
     */
    public static Map getUserOpenId(String appId, String secret, String jsCode) {
        /**
         *@desc 构建参数
         */
        HashMap<String, String> parameterMap = new HashMap<>(16);

        parameterMap.put("appid", appId);
        parameterMap.put("secret", secret);
        parameterMap.put("js_code", jsCode);
        parameterMap.put("grant_type", "authorization_code");
        /**
         *@desc 请求网络数据
         */
        String res = HttpUtils.get(PropertiesUtil.getProperty("GET_USER_OPEN_ID_URL"), parameterMap);
        Map<String, Object> resMap = JsonUtil.jsonToMap(res);
        if (resMap.get("openid") == null) {
            throw new RuntimeException("小程序code换取用户信息异常!返回结果:" + res);
        }
        return resMap;
    }


    /**
     * 微信jsSDK签名校验
     *
     * @param jsapi_ticket
     * @param url
     * @return
     */
    public static Map<String, String> sign(String jsapi_ticket, String url, String appId) {
        Map<String, String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String str;
        String signature = "";
        // 注意这里参数名必须全部小写，且必须有序
        str = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "&timestamp=" + timestamp + "&url=" + url;

        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(str.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);
        ret.put("appid", appId);

        return ret;
    }

    /**
     * 获取解密结果数据
     *
     * @param encryptedData
     * @param sessionKey
     * @param iv
     * @return
     */
    public static String decodeByWeChat(String encryptedData, String sessionKey, String iv) {
        /**
         *
         *@desc 被加密的数据
         */
        byte[] dataByte = Base64.getDecoder().decode(encryptedData);
        /**
         *
         *@desc 加密秘钥
         */
        byte[] keyByte = Base64.getDecoder().decode(sessionKey);
        /**
         *
         *@desc 偏移量
         */
        byte[] ivByte = Base64.getDecoder().decode(iv);

        try {
            /**
             *
             *@desc 如果密钥不足16位，那么就补足.  这个if 中的内容很重要
             */
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            /**
             *
             *@desc 初始化
             */
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            /**
             *
             *@desc 初始化
             */
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                return new String(resultByte, "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return null;
    }


    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

}
